﻿using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Moria;
using System.Windows.Controls.Primitives;
using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Linq;
using System.Windows.Media.Imaging;
using System.Diagnostics;
using Redbrick.Silverlight.UI;
using Moria.Common.Model;
using Moria.Common;


namespace Moria.Silverlight
{
	public class MapGrid : Panel
	{

		#region Declarations

		public static readonly DependencyProperty TileHeightProperty = DependencyProperty.Register("TileHeight", typeof(int), typeof(MapGrid), new PropertyMetadata(32));
		public static readonly DependencyProperty TileWidthProperty = DependencyProperty.Register("TileWidth", typeof(int), typeof(MapGrid), new PropertyMetadata(32));

		private static Dictionary<string, WriteableBitmap> _imageCache = new Dictionary<string, WriteableBitmap>();
		private static readonly WriteableBitmap _tileSource = null;

		private FrameworkElement _playerTile = null;
		private Rect _currentPanel = Rect.Empty;
		private double _vPageSize = 0.0;
		private double _hPageSize = 0.0;

		#endregion Declarations

		#region Constructors


		static MapGrid()
		{
			_tileSource = new WriteableBitmap(0, 0).FromResource("Assets/Tiles/32x32.png");
		}

		public MapGrid()
		{
			this.Loaded += new RoutedEventHandler(this.GridLoaded);
		}

		#endregion Constructors

		#region Methods

		protected override Size MeasureOverride(Size availableSize)
		{
			if (this.Moria == null || this.Moria.Cave == null) return Size.Empty;

			availableSize = new Size(this.TileWidth * this.Moria.Cave.Width,
											 this.TileHeight * this.Moria.Cave.Height);

			return availableSize;
		}

		protected override Size ArrangeOverride(Size finalSize)
		{
			foreach (FrameworkElement fe in this.Children)
			{
				Drawable item = fe.DataContext as Drawable;
				if (item != null)
				{
					fe.Arrange(new Rect(item.X * this.TileWidth,
												item.Y * this.TileHeight,
												this.TileWidth, this.TileHeight));
				}
			}

			return base.ArrangeOverride(finalSize);
		}

		private void InitGrid()
		{
			this.Width = TileWidth * this.Moria.Cave.Width;
			this.Height = TileHeight * this.Moria.Cave.Height;

			foreach (CaveFloor floor in this.Moria.Cave)
			{

				// add floor
				WriteableBitmap img = GetFloorTile(floor);
				AddTile(img, floor);

				// add items
				if (floor.HasItem)
				{
					InventoryItem invItem = floor.ItemPtr;
					img = GetItemTile(floor);
					AddTile(img, invItem);

				}

				// add creature
				if (floor.HasCreature)
				{
					Monster critter = floor.CreaturePtr;
					img = GetCritterTile(floor);
					AddTile(img, critter);
				}

			}

			// now, add the character tile
			WriteableBitmap pyTile = this.GetPlayerTile(this.Moria.Player.prace, this.Moria.Player.pclass);
			pyTile.DrawRectangle(0, 0, this.TileWidth - 1, this.TileHeight - 1, Colors.Red);

			AddTile(pyTile, Moria.Player);
			this.UpdateScrollPosition(this._playerTile);
		}

		private WriteableBitmap GetItemTile(CaveFloor floor)
		{
			int x = 0;
			int y = 0;
			InventoryItem invItem = floor.ItemPtr;

			switch (invItem.Type)
			{
				default:
					Debug.WriteLine("unknown item tile:" + invItem.Description);
					x = -1; y = -1; break;
				case ItemType.STORE_DOOR:
					if (invItem.SubCategory == 101) { x = 3; y = 26; }// General Store
					if (invItem.SubCategory == 102) { x = 4; y = 26; }// Armory
					if (invItem.SubCategory == 103) { x = 5; y = 26; }// Weapon Smiths
					if (invItem.SubCategory == 104) { x = 6; y = 26; }// Temple
					if (invItem.SubCategory == 105) { x = 7; y = 26; }// Alchemy Shop
					if (invItem.SubCategory == 106) { x = 8; y = 26; }// Magic Shop
					break;

				case ItemType.DOWN_STAIR: x = 22; y = 22; break;
				case ItemType.UP_STAIR: x = 21; y = 22; break;

			}

			WriteableBitmap img = GetTile(x, y);
			return img;
		}
		private WriteableBitmap GetCritterTile(CaveFloor floor)
		{
			int x = 0;
			int y = 0;
			CreatureSpecies critter = floor.CreaturePtr;
			Debug.WriteLine("");
			switch (critter.Symbol)
			{
				default:
					Debug.WriteLine("case " + critter.Variation + ": x=?; y=?; break; //" + critter.Name);
					x = -1; y = -1; break;
				case 'p':
					switch (critter.Variation)
					{
						default:
							Debug.WriteLine("case " + critter.Variation + ": x=?; y=?; break; //" + critter.Name);
							x = -1; y = -1; break;
						case 0: x = 0x80; y = 0x8B; break; //filthy street urchin
						case 1: x = 0x82; y = 0x8B; break; //Blubbering Idiot
						case 2: x = 0x85; y = 0x8B; break; //Pitiful-Looking Beggar
						case 3: x = 0x86; y = 0x8B; break; //Mangy-Looking Leper
						case 4: x = 0x87; y = 0x8B; break; //Squint-Eyed Rogue
						case 5: x = 0x88; y = 0x8B; break; //Singing, Happy Drunk
						case 6: x = 0x8A; y = 0x8B; break; //Mean-Looking Mercenary

					}
					break;
			}
			if (x > 0) x = x & 0x7F;
			if (y > 0) y = y & 0x7F;
			WriteableBitmap img = GetTile(x, y);
			return img;
		}
		private WriteableBitmap GetFloorTile(CaveFloor floor)
		{
			int x = 0;
			int y = 0;
			switch (floor.FloorType)
			{
				default:
					Debug.WriteLine("unknown floor tile:" + floor.Description);
					x = -1; y = -1; break;
				case FloorType.BoundaryWall: x = 1; y = 26; break;
				case FloorType.DarkFloor: x = 1; y = 23; break;
				case FloorType.GraniteWall: x = 76; y = 22; break;
				case FloorType.Corridor:
				case FloorType.LightFloor: x = 4; y = 23; break;
				case FloorType.MagamaWall: x = 52; y = 22; break;
				case FloorType.QuartzWall: x = 58; y = 22; break;
				case FloorType.BlockedFloor: x = 121; y = 121; break;
			}

			WriteableBitmap img = GetTile(x, y);
			return img;
		}
		private WriteableBitmap GetPlayerTile(int raceID, int classID)
		{
			int x = 0;
			int y = 0;
			switch (classID)
			{
				default:
					Debug.WriteLine("case " + classID + ": x=?; y=?; break; //" + this.Moria.Player.Class.title);
					x = -1; y = -1; break;
				case 0:// warrior
					switch (classID)
					{
						default:
							Debug.WriteLine("case " + raceID + ": x=?; y=?; break; //" + this.Moria.Player.Race.trace);
							x = -1; y = -1; break;
						case 0: x = 0x87; y = 0x83; break;  // human
						case 1: x = 0x8F; y = 0x83; break;	// half-elf
						case 2: x = 0x97; y = 0x83; break;	// elf
						case 3: x = 0x9F; y = 0x83; break;	// halfling
						case 4: x = 0xC7; y = 0x83; break;	// gnome
						case 5: x = 0xAF; y = 0x83; break;	// dwarf
						case 6: x = 0xB7; y = 0x83; break;	// half-orc
						case 7: x = 0xBF; y = 0x83; break;	// half-troll
					}

					break;
				case 1: break;	// mage
				case 2: break;	// priest
				case 3: break;	// Rogue
				case 4: break;	// Ranger
				case 5: break;	// Paladin
			}

			if (x > 0) x = x & 0x7F;
			if (y > 0) y = y & 0x7F;
			WriteableBitmap img = GetTile(x, y);
			return img;
		}

		private WriteableBitmap GetTile(int x, int y)
		{
			string key = x + "," + y;
			if (!_imageCache.ContainsKey(key))
			{
				WriteableBitmap bmp = new WriteableBitmap(this.TileWidth, this.TileHeight);
				if (x >= 0 && x >= 0)
				{
					Rect src = new Rect(x * this.TileWidth, y * this.TileHeight, this.TileWidth, this.TileHeight);
					bmp.Blit(new Rect(0, 0, this.TileWidth, this.TileHeight), _tileSource, src);
				}
				else
				{
					bmp.Clear(Colors.Green);
				}
				_imageCache[key] = bmp;
			}

			return _imageCache[key];
		}

		private void AddTile(ImageSource imgSource, Drawable item)
		{
			Image img = new Image()
			{
				DataContext = item,
				Source = imgSource,
				Width = this.TileWidth,
				Height = this.TileHeight,
			};


			ToolTipService.SetToolTip(img, item.Description);

			item.XPositionUpdated += new EventHandler<EventArgs>(item_PositionUpdated);
			item.YPositionUpdated += new EventHandler<EventArgs>(item_PositionUpdated);

			this.Children.Add(img);

			img.Arrange(new Rect(item.X * this.TileWidth,
										item.Y * this.TileHeight,
										this.TileWidth, this.TileHeight));

			if (img.DataContext is Player)
			{
				if (this._playerTile == null) _playerTile = img;
			}
		}

		private void UpdateScrollPosition(FrameworkElement focusedElement)
		{
			if (focusedElement == null) return;
			ScrollViewer scrollViewer = this.Parent as ScrollViewer;
			if (scrollViewer == null) return;

			if (this._currentPanel == Rect.Empty) InitView();

			Drawable mo = focusedElement.DataContext as Drawable;

			//TODO: tweak this
			if (mo != null)
			{

				Rect objRect = new Rect(mo.X * this.TileWidth, mo.Y * this.TileHeight, this.TileWidth, this.TileHeight);

				if (!this._currentPanel.Contains(objRect))
				{
					int dummy = 43;
					dummy += 23;

					double? hAdjust = null;
					if (objRect.Right > _currentPanel.Right)
					{
						hAdjust = _currentPanel.Left + (scrollViewer.ViewportWidth / 2);
					}
					else if (objRect.Left < _currentPanel.Left)
					{
						hAdjust = _currentPanel.Left - (scrollViewer.ViewportWidth / 2);
					}

					double? vAdjust = null;
					if (objRect.Bottom > _currentPanel.Bottom)
					{
						vAdjust = _currentPanel.Top + (scrollViewer.ViewportHeight / 2);
					}
					else if (objRect.Top < _currentPanel.Top)
					{
						vAdjust = _currentPanel.Top - (scrollViewer.ViewportHeight / 2);
					}


					if (hAdjust.HasValue)
					{
						if (hAdjust.Value < 0) hAdjust = 0;
						else if (hAdjust.Value > scrollViewer.ExtentWidth) hAdjust = scrollViewer.ExtentWidth;

						scrollViewer.ScrollToHorizontalOffset(hAdjust.Value);
						_currentPanel.X = hAdjust.Value;
					}

					if (vAdjust.HasValue)
					{
						if (vAdjust.Value < 0) vAdjust = 0;
						else if (vAdjust.Value > scrollViewer.ExtentHeight) vAdjust = scrollViewer.ExtentHeight;

						scrollViewer.ScrollToVerticalOffset(vAdjust.Value);
						_currentPanel.Y = vAdjust.Value;
					}

				}
			}
		}

		private void InitView()
		{
			ScrollViewer scrollViewer = this.Parent as ScrollViewer;
			if (scrollViewer != null)
			{
				this._vPageSize = scrollViewer.ViewportHeight / this.TileHeight;
				this._hPageSize = scrollViewer.ViewportWidth / this.TileWidth;

				//TODO: init this to the player's location
				this._currentPanel = new Rect(0, 0, (this._hPageSize - 1) * this.TileWidth,
													(this._vPageSize - 1) * this.TileHeight);
			}

		}

		#endregion Methods

		#region Events

		private void item_PositionUpdated(object sender, EventArgs e)
		{

			Drawable item = sender as Drawable;
			if (item != null)
			{
				FrameworkElement img = (from c in this.Children
												where c is FrameworkElement && ((FrameworkElement)c).DataContext == sender
												select c as FrameworkElement).FirstOrDefault();

				if (img != null)
				{


					img.Arrange(new Rect(item.X * this.TileWidth,
												item.Y * this.TileHeight,
												this.TileWidth, this.TileHeight));

					if (img.DataContext is Player)
					{
						if (this._playerTile == null) _playerTile = img;
						this.UpdateScrollPosition(img);
					}
				}
			}
		}
		private void Moria_NewLevelGenerated(object sender, EventArgs e)
		{
			this.InitGrid();
		}

		private void scrollViewer_SizeChanged(object sender, SizeChangedEventArgs e)
		{

			ScrollViewer scrollViewer = sender as ScrollViewer;
			if (scrollViewer != null)
			{
				this._vPageSize = (scrollViewer.ViewportHeight / this.TileHeight);
				this._hPageSize = (scrollViewer.ViewportWidth / this.TileWidth);
				this._currentPanel = new Rect(0, 0, this._hPageSize * this.TileWidth, this._vPageSize * this.TileHeight);

				//if (this._panels == null) this._panels = new List<Rect>();
				//else this._panels.Clear();

				//int hPanelCount = (int)Math.Ceiling(scrollViewer.ExtentWidth / scrollViewer.ViewportWidth);
				//int vPanelCount = (int)Math.Ceiling(scrollViewer.ExtentHeight / scrollViewer.ViewportHeight);

				//double panelWidth = scrollViewer.ExtentWidth / hPanelCount;
				//double panelHeight = scrollViewer.ExtentHeight / vPanelCount;

				//for (int x = 0; x < hPanelCount; x++)
				//{
				//   double xStart = x * panelWidth;
				//   double xWidth = xStart + panelWidth;

				//   for (int y = 0; y < vPanelCount; y++)
				//   {

				//      Rect sz = new Rect(xStart, y * panelHeight,
				//                         xWidth, (y + 1) * panelHeight);
				//      this._panels.Add(sz);
				//   }
				//}

			}
		}

		private void GridLoaded(object sender, EventArgs e)
		{
			if (!this.IsInDesignMode())	
			{
				this.Moria.NewLevelGenerated += new EventHandler<EventArgs>(Moria_NewLevelGenerated);

				if (this.Moria.Cave != null)
					InitGrid();

			}
		}


		#endregion Events

		#region Properties

		public int TileWidth
		{
			get { return (int)GetValue(TileWidthProperty); }
			set { SetValue(TileWidthProperty, value); }
		}
		public int TileHeight
		{
			get { return (int)GetValue(TileHeightProperty); }
			set { SetValue(TileHeightProperty, value); }
		}

		private TheGame Moria { get { return this.DataContext as TheGame; } }

		#endregion Properties
	}
}
